﻿using CK.Sprite.Framework;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CK.Sprite.Form.Core
{
    /*
     微服务如果是本地调用，且事物执行，则将unitofwork信息传递到微服务
     规则定义如下：
        如果点击按钮，执行：方法1;方法2 事物执行：方法3;方法4;方法5 执行：更新文本框样式...
         */

    public class RuntimeService : DomainService
    {
        private readonly SpriteObjectLocalCache _objectLocalCache;
        private readonly ObjectMethodLocalCache _methodLocalCache;

        public RuntimeService(SpriteObjectLocalCache objectLocalCache,
            ObjectMethodLocalCache methodLocalCache)
        {
            _objectLocalCache = objectLocalCache;
            _methodLocalCache = methodLocalCache;
        }

        public async Task<JArray> CallRuntimeMethod(JObject paramObject)
        {
            CheckRuntimeMethods(paramObject, out string applicationCode, out bool isTransaction);
            var methods = paramObject["methods"].Children();
            if (isTransaction)
            {
                var businessConfig = await TenantConfigStore.FindAsync(applicationCode, _serviceProvider.GetService<ICurrentTenant>().TenantCode);
                return await _serviceProvider.DoDapperServiceAsync<JArray>(businessConfig, async (unitOfWork) =>
                {
                    return await DoRuntimeMethodAsync(methods, applicationCode, unitOfWork, businessConfig);
                });
            }
            else
            {
                return await DoRuntimeMethodAsync(methods, applicationCode, null, null);
            }
        }

        private async Task<JArray> DoRuntimeMethodAsync(JEnumerable<JToken> methods, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig)
        {
            JArray jResult = new JArray();
            foreach (var method in methods)
            {
                var strMethodId = method["methodId"].ToString();
                Guid.TryParse(strMethodId, out var methodId);
                var objectName = method["objectName"].ToString();
                var spriteObjectDto = _objectLocalCache.GetAll(applicationCode).FirstOrDefault(r => r.Name == objectName);
                if (spriteObjectDto == null)
                {
                    throw new SpriteException($"未找到{objectName}数据库对象名称");
                }

                if (methodId == default) // 不是Guid，则根据传入的名称执行默认Sql方法
                {
                    await DoDefaultSqlMethod(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto, jResult);
                }
                else
                {
                    var methodInfo = _methodLocalCache.GetAll(applicationCode).FirstOrDefault(r => r.Id == methodId);
                    if (methodInfo == null)
                    {
                        throw new SpriteException($"未找到{objectName}对应的方法");
                    }
                    if (methodInfo.MethodExeType == EMethodExeType.Sql) // Sql方法
                    {

                    }
                }
            }

            return jResult;
        }

        private async Task DoDefaultSqlMethod(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto, JArray jResult)
        {
            var strMethodId = method["methodId"].ToString();
            if(strMethodId == SpriteObjectConsts.DefaultCreateOrUpdateMethodName)
            {
                if(method["datas"]["paramValues"]["id"] == null)
                {
                    strMethodId = SpriteObjectConsts.DefaultCreateMethodName;
                }
                else
                {
                    strMethodId = SpriteObjectConsts.DefaultUpdateMethodName;
                }
            }
            var ruleId = method["ruleId"];
            switch (strMethodId) // 默认方法直接存储名称，否则存储方法Guid
            {
                case SpriteObjectConsts.DefaultCreateMethodName:
                    var defaultCreateResult = await DoDefaultCreateMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultCreateResult.Add("ruleId", ruleId);
                    jResult.Add(defaultCreateResult);
                    break;
                case SpriteObjectConsts.DefaultUpdateMethodName:
                    var defaultUpdateResult = await DoDefaultUpdateMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultUpdateResult.Add("ruleId", ruleId);
                    jResult.Add(defaultUpdateResult);
                    break;
                case SpriteObjectConsts.DefaultDeleteMethodName:
                    var defaultDeleteResult = await DoDefaultDeleteMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultDeleteResult.Add("ruleId", ruleId);
                    jResult.Add(defaultDeleteResult);
                    break;
                case SpriteObjectConsts.DefaultGetMethodName:
                    var defaultGetResult = await DoDefaultGetMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultGetResult.Add("ruleId", ruleId);
                    jResult.Add(defaultGetResult);
                    break;
                case SpriteObjectConsts.DefaultListMethodName:
                    var defaultListResult = await DoDefaultListMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultListResult.Add("ruleId", ruleId);
                    jResult.Add(defaultListResult);
                    break;
                case SpriteObjectConsts.DefaultUpdateWhereMethodName:
                    var defaultUpdateWhereResult = await DoDefaultUpdateWhereMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultUpdateWhereResult.Add("ruleId", ruleId);
                    jResult.Add(defaultUpdateWhereResult);
                    break;
                case SpriteObjectConsts.DefaultDeleteWhereMethodName:
                    var defaultDeleteWhereResult = await DoDefaultDeleteWhereMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultDeleteWhereResult.Add("ruleId", ruleId);
                    jResult.Add(defaultDeleteWhereResult);
                    break;
                case SpriteObjectConsts.DefaultGetWhereMethodName:
                    var defaultGetWhereResult = await DoDefaultGetWhereMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultGetWhereResult.Add("ruleId", ruleId);
                    jResult.Add(defaultGetWhereResult);
                    break;
                case SpriteObjectConsts.DefaultListWhereMethodName:
                    var defaultListWhereResult = await DoDefaultListWhereMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultListWhereResult.Add("ruleId", ruleId);
                    jResult.Add(defaultListWhereResult);
                    break;
                case SpriteObjectConsts.DefaultTreeListWhereMethodName:
                    if (!spriteObjectDto.IsTree)
                    {
                        throw new SpriteException("方法执行错误，数据表非树结构");
                    }
                    var defaultTreeListWhereResult = await DoDefaultTreeListWhereMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultTreeListWhereResult.Add("ruleId", ruleId);
                    jResult.Add(defaultTreeListWhereResult);
                    break;
                case SpriteObjectConsts.DefaultPageListMethodName:
                    var defaultPageListResult = await DoDefaultPageListMethodAsync(method, applicationCode, unitOfWork, tenantConfig, spriteObjectDto);
                    defaultPageListResult.Add("ruleId", ruleId);
                    jResult.Add(defaultPageListResult);
                    break;
                default:
                    break;
            }
        }

        #region 默认方法执行

        private async Task<JObject> DoDefaultMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, Func<IRuntimeRepository, JToken, Task<JObject>> func)
        {
            JToken datas = method["datas"];
            if (unitOfWork == null)
            {
                var businessConfig = await TenantConfigStore.FindAsync(applicationCode, _serviceProvider.GetService<ICurrentTenant>().TenantCode);
                return await _serviceProvider.DoDapperServiceAsync(businessConfig, async (unitOfWorkNew) =>
                {
                    var runtimeRepository = ConnectionFactory.GetConnectionProvider(tenantConfig.ApplicationCode).GetRepository<IRuntimeRepository>(unitOfWorkNew);
                    var funcResult = await func(runtimeRepository, datas);
                    return funcResult;
                });
            }
            else
            {
                var runtimeRepository = ConnectionFactory.GetConnectionProvider(tenantConfig.ApplicationCode).GetRepository<IRuntimeRepository>(unitOfWork);
                var funcResult = await func(runtimeRepository, datas);
                return funcResult;
            }
        }

        // 执行默认Create方法
        private async Task<JObject> DoDefaultCreateMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultCreateMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>());
            });
        }

        // 执行默认Update方法
        private async Task<JObject> DoDefaultUpdateMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultUpdateMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>());
            });
        }

        // 执行默认Delete方法
        private async Task<JObject> DoDefaultDeleteMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultDeleteMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>());
            });
        }

        // 执行默认Get方法
        private async Task<JObject> DoDefaultGetMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultGetMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlFields"]?.ToObject<JArray>());
            });
        }

        // 执行默认List方法
        private async Task<JObject> DoDefaultListMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultListMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlFields"]?.ToObject<JArray>(), datas["sqlOrderBys"]);
            });
        }

        // 执行默认Update Where方法
        private async Task<JObject> DoDefaultUpdateWhereMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultUpdateWhereMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"]);
            });
        }

        // 执行默认Delete方法
        private async Task<JObject> DoDefaultDeleteWhereMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultDeleteWhereMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"]);
            });
        }

        // 执行默认Get Where方法
        private async Task<JObject> DoDefaultGetWhereMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultGetWhereMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"], datas["sqlFields"]?.ToObject<JArray>());
            });
        }

        // 执行默认List Where方法
        private async Task<JObject> DoDefaultListWhereMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultListWhereMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"], datas["sqlFields"]?.ToObject<JArray>(), datas["sqlOrderBys"]);
            });
        }

        // 执行默认List Where方法
        private async Task<JObject> DoDefaultTreeListWhereMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                var queryResult = await runtimeRepository.DoDefaultListWhereMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"], datas["sqlFields"]?.ToObject<JArray>(), datas["sqlOrderBys"]);

                var queryDatas = queryResult["result"].ToObject<JArray>();
                var treeCodes = queryDatas.Select(r => r["treeCode"].ToString());
                var parentIds = new List<string>();
                foreach (var treeCode in treeCodes)
                {
                    var tempIds = treeCode.Substring(2).Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                    parentIds.AddRange(tempIds);
                }

                parentIds = parentIds.Distinct().ToList();
                parentIds = parentIds.Where(r => !queryDatas.Any(t => t["id"].ToString() == r)).ToList();

                if (parentIds.Count > 0)
                {
                    var expressSqlModel = new ExpressSqlModel()
                    {
                        SqlExpressType = ESqlExpressType.And,
                        Children = new List<ExpressSqlModel>()
                        {
                            new ExpressSqlModel()
                            {
                                Field = "Id",
                                ConditionType = EConditionType.In,
                                SqlExpressType = ESqlExpressType.Condition,
                                Value = parentIds
                            }
                        }
                    };

                    var parentQueryResult = await runtimeRepository.DoDefaultListWhereMethodAsync(spriteObjectDto, null, JObject.FromObject(expressSqlModel, ExpressSqlHelper.CreateCamelCaseJsonSerializer()), datas["sqlFields"]?.ToObject<JArray>(), null);

                    var parentQueryDatas = parentQueryResult["result"].ToObject<JArray>();
                    if (parentQueryDatas != null && parentQueryDatas.Count > 0)
                    {
                        foreach (var parentQueryData in parentQueryDatas)
                        {
                            queryDatas.Add(parentQueryData);
                        }
                    }
                }

                var resultDatas = new JArray();
                var rootDatas = queryDatas.Where(r => r["treeCode"].ToString() == "0.").ToList();
                foreach (var rootData in rootDatas)
                {
                    CreateTreeList(queryDatas, rootData as JObject);
                    CreateVueTreeInfo(rootData as JObject);
                    resultDatas.Add(rootData);
                }

                queryResult["result"] = resultDatas;

                return queryResult;
            });
        }

        private void CreateTreeList(JArray treeDatas, JObject parentData)
        {
            var childDatas = treeDatas.Where(r => r["pId"].ToString() == parentData["id"].ToString()).ToList();
            if (childDatas.Count > 0)
            {
                parentData.Add(new JProperty("children", new JArray()));
                foreach (var childData in childDatas)
                {
                    CreateTreeList(treeDatas, childData as JObject);
                    CreateVueTreeInfo(childData as JObject);
                    (parentData["children"] as JArray).Add(childData);
                }
            }
        }

        private void CreateVueTreeInfo(JObject jObject)
        {
            jObject.Add(new JProperty("key", jObject["id"]));
            jObject.Add(new JProperty("value", jObject["id"]));
        }

        // 执行默认Page List方法
        private async Task<JObject> DoDefaultPageListMethodAsync(JToken method, string applicationCode, IUnitOfWork unitOfWork, TenantConfig tenantConfig, SpriteObjectDto spriteObjectDto)
        {
            return await DoDefaultMethodAsync(method, applicationCode, unitOfWork, tenantConfig, async (runtimeRepository, datas) =>
            {
                return await runtimeRepository.DoDefaultPageListMethodAsync(spriteObjectDto, datas["paramValues"]?.ToObject<JObject>(), datas["sqlWheres"], datas["sqlFields"]?.ToObject<JArray>(), datas["sqlOrderBys"], datas["sqlMaxResultCount"], datas["sqlSkipCount"]);
            });
        }

        #endregion

        private void CheckRuntimeMethods(JObject paramObject, out string applicationCode, out bool isTransaction)
        {
            if (!paramObject.ContainsKey("applicationCode"))
            {
                throw new SpriteException("方法调用未传递应用编码");
            }

            if (!paramObject.ContainsKey("methods"))
            {
                throw new SpriteException("方法调用未传递调用方法");
            }

            applicationCode = paramObject["applicationCode"].ToString();

            isTransaction = false;
            if (paramObject.ContainsKey("isTransaction"))
            {
                isTransaction = paramObject["isTransaction"].Value<bool>();
            }
        }
    }
}
